#include "dag.h"

#include <string.h>
#include <algorithm>
#include <iostream>

using namespace dag;
using namespace std;

FrameDepot::FrameDepot(const FrameSpec& spec)
:	m_Spec(spec),
	m_NumFrames(0)
{
}

FrameDepot::FrameDepot(const FrameDepot& copy) 
:	m_Spec(copy.m_Spec),
	m_NumFrames(0)
{
	*this = copy;
}

FrameDepot::~FrameDepot() throw() {
	try {
		clear();
	}
	catch (...) {
	}
}

FrameDepot& FrameDepot::operator=(const FrameDepot& rhs) {
	/* check for self assignment */
	if (this == &rhs) {
		return *this;
	}
	
	/* remove old data and add in new data */
	clear();
	
	/* copy new data */
	m_Spec = rhs.m_Spec;
	 
	bool *bTemp = NULL;
	int *iTemp = NULL;
	float *fTemp = NULL;
	double *dTemp = NULL;
	
	try {
		/* Have to copy vector data row by row.  Also must make sure that in
		 * case of exception there is no memory link */
		for (size_t i = 0; i < rhs.m_BData.size(); i++) {
			bTemp = new bool[m_Spec.bSize];
			memcpy(bTemp, rhs.m_BData[i], sizeof(bool) * m_Spec.bSize);
			m_BData.push_back(bTemp);
			bTemp = NULL;
		}
		
		for (size_t i = 0; i < rhs.m_IData.size(); i++) {
			iTemp = new int[m_Spec.iSize];
			memcpy(iTemp, rhs.m_IData[i], sizeof(int) * m_Spec.iSize);
			m_IData.push_back(iTemp);
			iTemp = NULL;
		}
		
		for (size_t i = 0; i < rhs.m_FData.size(); i++) {
			fTemp = new float[m_Spec.fSize];
			memcpy(fTemp, rhs.m_FData[i], sizeof(float) * m_Spec.fSize);
			m_FData.push_back(fTemp);
			fTemp = NULL;
		}
		
		for (size_t i = 0; i < rhs.m_DData.size(); i++) {
			dTemp = new double[m_Spec.dSize];
			memcpy(dTemp, rhs.m_DData[i], sizeof(double) * m_Spec.dSize);
			m_DData.push_back(dTemp);
			dTemp = NULL;
		}
		
		/* get the number of rames by finding the vector with the maximum number
		 * of inputs */
		m_NumFrames = max(m_DData.size(), m_FData.size());
		m_NumFrames = max(m_IData.size(), m_NumFrames);
		m_NumFrames = max(m_BData.size(), m_NumFrames);
		
		/* ensure that all vectors either contain o frames, or equal number of 
		 * frames */
		if (m_BData.size() > 0) {
			if (m_NumFrames != m_BData.size() ) {
				throw InvalidArgument();
			}
		}
		else if (m_IData.size() > 0) {
			if (m_NumFrames != m_IData.size() ) {
				throw InvalidArgument();
			}
		}
		else if (m_FData.size() > 0) {
			if (m_NumFrames != m_FData.size() ) {
				throw InvalidArgument();
			}
		}
		else if (m_DData.size() > 0) {
			if (m_NumFrames != m_DData.size() ) {
				throw InvalidArgument();
			}
		}
	}
	catch (...) {
		delete[] bTemp;
		delete[] iTemp;
		delete[] fTemp;
		delete[] dTemp;
		clear();
		throw;
	}
	
	return *this;
}

size_t FrameDepot::size() const {
	return m_NumFrames;
}

void FrameDepot::clear() {
	m_NumFrames = 0;
	
	for (size_t i = 0; i < m_BData.size(); i++) {
		delete[] m_BData[i];
		m_BData[i] = NULL;
	}
	m_BData.clear();
	
	for (size_t i = 0; i < m_IData.size(); i++) {
		delete[] m_IData[i];
		m_IData[i] = NULL;
	}
	m_IData.clear();
	
	for (size_t i = 0; i < m_FData.size(); i++) {
		delete[] m_FData[i];
		m_FData[i] = NULL;
	}
	m_FData.clear();
	
	for (size_t i = 0; i < m_DData.size(); i++) {
		delete[] m_DData[i];
		m_DData[i] = NULL;
	}
	m_DData.clear();
}

const bool* FrameDepot::bools(size_t pos) const {
	return m_BData[pos];
}

const int* FrameDepot::ints(size_t pos) const {
	return m_IData[pos];
}

const float* FrameDepot::floats(size_t pos) const {
	return m_FData[pos];
}

const double* FrameDepot::doubles(size_t pos) const {
	return m_DData[pos];
}

const FrameSpec& FrameDepot::spec() const {
	return m_Spec;
}

void FrameDepot::fill(size_t pos, Frame& frame) const {
	if (frame.bSize != m_Spec.bSize ||
		frame.iSize != m_Spec.iSize ||
		frame.fSize != m_Spec.fSize ||
		frame.dSize != m_Spec.dSize) {
		throw InvalidArgument();
	}

	if (frame.bSize > 0) {
		memcpy(frame.bools, m_BData[pos], sizeof(bool) * frame.bSize);
	}
	
	if (frame.iSize > 0) {
		memcpy(frame.ints, m_IData[pos], sizeof(int) * frame.iSize);
	}
	
	if (frame.fSize > 0) {
		memcpy(frame.floats, m_FData[pos], sizeof(float) * frame.fSize);
	}
	
	if (frame.dSize > 0) {
		memcpy(frame.doubles, m_DData[pos], sizeof(double) * frame.dSize);
	}
}

void FrameDepot::add(const Frame& frame) {
	/* validate frame spec */
	if (frame.bSize != m_Spec.bSize ||
		frame.iSize != m_Spec.iSize ||
		frame.fSize != m_Spec.fSize ||
		frame.dSize != m_Spec.dSize) {
		throw InvalidArgument();
	}
	
	bool *bTemp = NULL;
	int *iTemp = NULL;
	float *fTemp = NULL;
	double *dTemp = NULL;
	
	try {

		
		if (m_Spec.bSize > 0) {
			bTemp = new bool[m_Spec.bSize];
			memcpy(bTemp, frame.bools, m_Spec.bSize * sizeof(bool));
			m_BData.push_back(bTemp);
			bTemp = NULL;
		}
		
		if (m_Spec.iSize > 0) {
			iTemp = new int[m_Spec.iSize];
			memcpy(iTemp, frame.ints, m_Spec.iSize * sizeof(int));
			m_IData.push_back(iTemp);
			iTemp = NULL;			
		}
		
		if (m_Spec.fSize > 0) {
			fTemp = new float[m_Spec.fSize];
			memcpy(fTemp, frame.floats, m_Spec.fSize * sizeof(float));
			m_FData.push_back(fTemp);
			fTemp = NULL;
		}
		
		if (m_Spec.dSize > 0) {
			dTemp = new double[m_Spec.dSize];
			memcpy(dTemp, frame.doubles, m_Spec.dSize * sizeof(double));
			m_DData.push_back(dTemp);
			dTemp = NULL;
		}
		
		if ((m_Spec.bSize || m_Spec.iSize || m_Spec.fSize || m_Spec.dSize) > 0) {
			m_NumFrames++;
		}
	}
	catch (...) {
		delete[] bTemp;
		delete[] iTemp;
		delete[] fTemp;
		delete[] dTemp;
		
		while (m_BData.size() > m_NumFrames) {
			delete[] m_BData.back();
			m_BData.pop_back();
		}
		while (m_IData.size() > m_NumFrames) {
			delete[] m_IData.back();
			m_IData.pop_back();
		}
		while (m_FData.size() > m_NumFrames) {
			delete[] m_FData.back();
			m_FData.pop_back();
		}
		while (m_DData.size() > m_NumFrames) {
			delete[] m_DData.back();
			m_DData.pop_back();
		}
		
		throw;
	}
}

void FrameDepot::serialize(ostream& binaryStream) const {
	m_Spec.serialize(binaryStream);
	int numFrames = (int)m_NumFrames;
	binaryStream.write((const char*)&numFrames, sizeof(int));
	Frame frame(m_Spec);
	for (size_t i = 0; i < m_NumFrames; i++) {
		fill(i, frame);
		frame.serialize(binaryStream);
	}
}

auto_ptr<FrameDepot> FrameDepot::inflate(istream& binaryStream) {
	auto_ptr<FrameDepot> frameDepot(NULL);
	auto_ptr<FrameSpec> frameSpec = FrameSpec::inflate(binaryStream);
	if (frameSpec.get() == NULL) {
		throw NullPtr();
	}
	frameDepot.reset(new FrameDepot(*frameSpec));
	int numFrames = -1;
	binaryStream.read((char*)&numFrames, sizeof(int));
	if (numFrames < 0) {
		throw InvalidValue();
	}

	for (int i = 0; i < numFrames; i++) {
		auto_ptr<Frame> frame(NULL);
		frame = Frame::inflate(binaryStream);
		if (frame.get() == NULL) {
			throw NullPtr();
		}
		frameDepot->add(*frame);
	}
	return frameDepot;
}
